#include "models/worker_impl.h"
#include "helpers/exception_handler.h"

#include "rive/audio/audio_engine.hpp"
#include "rive/renderer/gl/render_target_gl.hpp"

#include "napi/native_api.h"
#include <native_window/external_window.h>

namespace ohos_rive {
std::unique_ptr<WorkerImpl> WorkerImpl::Make(SurfaceVariant surface,
                                             DrawableThreadState *threadState,
                                             const RendererType type)
{
    assert(surface.index() > 0); // Valid surface?

    bool success = false;
    std::unique_ptr<WorkerImpl> impl;
    switch (type) {
        case RendererType::Rive: {
            EGLNativeWindowType window = std::get<EGLNativeWindowType>(surface);
            impl = std::make_unique<PLSWorkerImpl>(window, threadState, &success);
            break;
        }
        case RendererType::Canvas: {
            napi_ref napiSurface = std::get<napi_ref>(surface);
            impl = std::make_unique<CanvasWorkerImpl>(napiSurface, &success);
            break;
        }
        default:
            break;
    }
    if (!success) {
        impl->destroy(threadState);
        impl.reset();
    }
    return impl;
}

void WorkerImpl::start(std::chrono::high_resolution_clock::time_point frameTime)
{
    m_lastFrameTime = frameTime;
    m_isStarted = true;

#ifdef WITH_AUDIO
    if (auto engine = rive::AudioEngine::RuntimeEngine(false)) {
        engine->start();
    }
#endif
}

void WorkerImpl::stop()
{
    if (!m_isStarted) {
        return;
    }

#ifdef WITH_RIVE_AUDIO
    if (auto engine = rive::AudioEngine::RuntimeEngine(false)) {
        engine->stop();
    }
#endif

    m_isStarted = false;
}

void WorkerImpl::doFrame(ITracer *tracer,
                         DrawableThreadState *threadState,
                         std::chrono::high_resolution_clock::time_point frameTime,
                         napi_threadsafe_function worker_tsfn)
{
    auto timestamp_ms = std::chrono::time_point_cast<std::chrono::milliseconds>(frameTime);
    auto value_ms = timestamp_ms.time_since_epoch().count();
    if (!m_isStarted) {
        LOGE("Worker not started, skipping frame");
        return;
    }

    float elapsedMs = std::chrono::duration<float>(frameTime - m_lastFrameTime).count();
    m_lastFrameTime = frameTime;

    if (worker_tsfn != nullptr) {
        auto *advanceData = new TSFNData{TSFNData::Type::ADVANCE};
        advanceData->elapsedMs = elapsedMs;
        advanceData->frameTime = frameTime;

        std::mutex mutex;
        std::condition_variable cv;
        bool advanceCompleted = false;
        advanceData->completionCallback = [&]() {
            std::lock_guard<std::mutex> lock(mutex);
            advanceCompleted = true;
            cv.notify_one();
        };

        napi_call_threadsafe_function(worker_tsfn, advanceData, napi_tsfn_blocking);
        std::unique_lock<std::mutex> lock(mutex);
        cv.wait(lock, [&]() { return advanceCompleted; });
    }

    tracer->beginSection("draw()");
    prepareForDraw(threadState);

    if (worker_tsfn != nullptr) {
        auto *drawData = new TSFNData{TSFNData::Type::DRAW};

        std::mutex mutex;
        std::condition_variable cv;
        bool drawCompleted = false;
        drawData->completionCallback = [&]() {
            std::lock_guard<std::mutex> lock(mutex);
            drawCompleted = true;
            cv.notify_one();
        };

        napi_call_threadsafe_function(worker_tsfn, drawData, napi_tsfn_blocking);
        std::unique_lock<std::mutex> lock(mutex);
        cv.wait(lock, [&]() { return drawCompleted; });
    }

    tracer->beginSection("flush()");
    flush(threadState);
    tracer->endSection(); // flush

    tracer->beginSection("swapBuffers()");
    threadState->swapBuffers();
    tracer->endSection(); // swapBuffers

    tracer->endSection(); // doFrame
}

/* PLSWorkerImpl */
PLSWorkerImpl::PLSWorkerImpl(EGLNativeWindowType window, DrawableThreadState *threadState, bool *success)
    : EGLWorkerImpl(window, threadState, success)
{
    if (!success) {
        LOGE("EGLWorkerImpl creation failed");
        return;
    }

    auto eglThreadState = static_cast<EGLThreadState *>(threadState);

    eglThreadState->makeCurrent(m_eglSurface);
    rive::gpu::RenderContext *renderContext = PLSWorkerImpl::PlsThreadState(eglThreadState)->renderContext();
    if (renderContext == nullptr) {
        LOGE("PLS render context is null, PLS not supported");
        return; // PLS was not supported.
    }

    int32_t width = 0;
    int32_t height = 0;
    OH_NativeWindow_NativeWindowHandleOpt(reinterpret_cast<OHNativeWindow *>(window), GET_BUFFER_GEOMETRY, &height,
                                          &width);

    GLint sampleCount;
    glBindFramebuffer(GL_FRAMEBUFFER, 0);
    glGetIntegerv(GL_SAMPLES, &sampleCount);
    m_renderTarget = rive::make_rcp<rive::gpu::FramebufferRenderTargetGL>(width, height, 0, sampleCount);
    m_plsRenderer = std::make_unique<rive::RiveRenderer>(renderContext);
    *success = true;
}

void PLSWorkerImpl::destroy(DrawableThreadState *threadState)
{
    m_plsRenderer.reset();
    m_renderTarget.reset();
    EGLWorkerImpl::destroy(threadState);
}

void PLSWorkerImpl::clear(DrawableThreadState *threadState) const
{
    PLSThreadState *plsThreadState = PLSWorkerImpl::PlsThreadState(threadState);
    rive::gpu::RenderContext *renderContext = plsThreadState->renderContext();
    renderContext->beginFrame({
        .renderTargetWidth = m_renderTarget->width(),
        .renderTargetHeight = m_renderTarget->height(),
        .loadAction = rive::gpu::LoadAction::clear,
        .clearColor = 0,
    });
}

void PLSWorkerImpl::flush(DrawableThreadState *threadState) const
{
    PLSThreadState *plsThreadState = PLSWorkerImpl::PlsThreadState(threadState);
    rive::gpu::RenderContext *renderContext = plsThreadState->renderContext();
    renderContext->flush({.renderTarget = m_renderTarget.get()});
}

rive::Renderer *PLSWorkerImpl::renderer() const
{
    return m_plsRenderer.get();
}

/* CanvasWorkerImpl */
void CanvasWorkerImpl::destroy(DrawableThreadState *)
{
    assert(m_napiSurfaceRef != nullptr);

    m_canvasRenderer.reset();
    napi_delete_reference(m_env, m_napiSurfaceRef);
    m_napiSurfaceRef = nullptr;
}

void CanvasWorkerImpl::prepareForDraw(DrawableThreadState *) const
{
    napi_value napiSurface;
    napi_status status = napi_get_reference_value(m_env, m_napiSurfaceRef, &napiSurface);
    if (status != napi_ok) {
        LOGE("Failed to get napiSurface from reference");
        return;
    }

    OH_Drawing_Surface *surface = reinterpret_cast<OH_Drawing_Surface *>(napiSurface);
    m_canvasRenderer->bindCanvas(surface);
}

void CanvasWorkerImpl::flush(DrawableThreadState *) const
{
    napi_value napiSurface;
    napi_status status = napi_get_reference_value(m_env, m_napiSurfaceRef, &napiSurface);
    if (status != napi_ok) {
        LOGE("Failed to get napiSurface from m_napiSurfaceRef");
        return;
    }

    OH_Drawing_Surface *surface = reinterpret_cast<OH_Drawing_Surface *>(napiSurface);
    m_canvasRenderer->unlockAndPost(surface);
}
} // namespace ohos_rive